iT邦幫忙

2023 iThome 鐵人賽

DAY 21
0

當一篇文章的篇幅太長時,若能及早知道他的結構,會更幫助讀者快速了解哪部分是自己當下需要閱讀的,這時候若在文章側邊有附上一個綱要與連結,就能滿足這個需求。

在落實這個功能前,先透過前幾天提到的〈將常用的格式元件化〉重構 @/components/TheArticles.vue,將其內容拆成 <PostHeader /><PostContent /><PostFooter /> 三個元件,讓日後維護更能快速找到想要修改的地方。

<template>
  <article class="pb-4">
     <PostHeader :post="post"/>
     <PostContent :post="post"/>
     <PostFooter :post="post"/>
   </article>
</template>
<script setup lang="ts">
import { Post } from '@/types/index'

defineProps<{
 post: Post
}>()
</script>

來到 @/components/Post/Content.vue,原本的程式碼在重構後是長這樣:

<template>
  <div class="my-4 break-word ">
    <ContentRenderer :value="post" class="prose" />
  </div>
</template>

<script setup lang="ts">
import { Post } from '@/types/index'

defineProps<{
  post: Post
}>()
</script>

接著透過 TailwindCSS 的 Flex Direction .flex .flex-row-reverse 和 Basis,將內容的區塊分成四等分,其中一等分 .basis-1/4 提供給綱要,另外三等份 .basis-3/4 用在文章的檢視上。

並在那四分之一中建立 <PostTableOfContent /> 的元件,並預期會傳入一個 toc 的 Props 進去。這邊可以採用 Nuxt Content 在 MarkdownParsedContent 內建的 body.toc.links 參數傳進去,只要是內容來自於 Markdown 類型的檔案,都會提供這個參數,而這個參數是 TocLink 型別,晚點會提到。

<template>
  <div class="my-4 break-word flex flex-row-reverse">
    <div class="basis-1/4">
      <PostTableOfContent
        class="my-4"
        :toc="post.body.toc.links"
      ></PostTableOfContent>
    </div>
    <div class="basis-3/4 mr-4">
      <ContentRenderer :value="post" class="prose" />
    </div>
  </div>
</template>

<script setup lang="ts">
import { Post } from '@/types/index'

defineProps<{
  post: Post
}>()
</script>

然後就來實作 <TableOfContent />,在介紹程式碼前,先講一下上面提到的 TocLink 型別:

export interface TocLink {
  id: string
  text: string
  depth: number
  children?: TocLink[]
}

每一個 TocLink,也都會有一個 TocLink 型別的 children 屬性。也就是每個二級標題之下的三級標題,都是其 children,三級之下的四級標題也是其 children,以此類推。

在 Markdown,通常一級標題 # 就是該文章的標題,所以在內文中的標題會從二級標題開始 ## 使用。

了解其概念後,開始建立 @/components/Post/TableOfContent.vue

<template>
  <div v-if="toc.length > 0">
    <div class="text-2xl">ToC</div>

    <ol class="list-outside text-sm">
      <template v-for="primaryLink of toc" :key="primaryLink.id">
        <li>
          <NuxtLink
            class="font-normal text-slate-700"
            :to="`#${primaryLink.id}`"
          >
            {{ primaryLink.text }}
          </NuxtLink>
        </li>
        <ol v-if="primaryLink.children" class="list-outside text-sm">
          <li
            v-for="secondaryLink of primaryLink.children"
            :key="secondaryLink.id"
          >
            <NuxtLink
              class="font-normal text-slate-700"
              :to="`#${secondaryLink.id}`"
            >
              {{ secondaryLink.text }}
            </NuxtLink>
          </li>
        </ol>
      </template>
    </ol>
  </div>
</template>

<script setup lang="ts">
import type { TocLink } from '@nuxt/content/dist/runtime/types'

const props = defineProps<{
  toc: TocLink[]
}>()
</script>

為了使用 TocLink 型別,可以從 '@nuxt/content/dist/runtime/types' 引入。

要為綱要設立連結,可以善用 TocLink.id,Nuxt Content 在渲染 Markdown 的標題時,會使用該 id 在標題標籤上。

接著只要透過 Vue 本身的 List Rendering 就可以將目錄給渲染出來了。效果如下圖:

點選綱要的標題連結,就會跳到該標題的位置:


上一篇
為首頁新增內文與近期發表的文章
下一篇
透過外掛實作將 Wikilink 轉換成 Markdown Link
系列文
用 Nuxt Content 搭配 Obsidian 建立自己的 Digital Garden30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言